<?php

declare(strict_types=1);

namespace PhpMyAdmin\Tests\Triggers;

use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Triggers\Trigger;
use PhpMyAdmin\Triggers\Triggers;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;

#[CoversClass(Trigger::class)]
#[CoversClass(Triggers::class)]
class TriggersTest extends AbstractTestCase
{
    private Triggers $triggers;

    /**
     * Set up
     */
    protected function setUp(): void
    {
        parent::setUp();

        parent::setGlobalConfig();

        parent::setLanguage();

        parent::setTheme();

        $GLOBALS['dbi'] = $this->createDatabaseInterface();
        $GLOBALS['server'] = 0;
        $GLOBALS['cfg']['Server']['DisableIS'] = false;
        $GLOBALS['db'] = 'pma_test';
        $GLOBALS['table'] = 'table';

        $this->triggers = new Triggers(
            $GLOBALS['dbi'],
            new Template(),
            ResponseRenderer::getInstance(),
        );
    }

    /**
     * Test for getDataFromRequest
     *
     * @param array<string, string> $in  Input
     * @param array<string, string> $out Expected output
     */
    #[DataProvider('providerGetDataFromRequestEmpty')]
    public function testGetDataFromRequestEmpty(array $in, array $out): void
    {
        unset($_POST);
        foreach ($in as $key => $value) {
            if ($value === '') {
                continue;
            }

            $_POST[$key] = $value;
        }

        $this->assertEquals($out, $this->triggers->getDataFromRequest());
    }

    /**
     * Data provider for testGetDataFromRequestEmpty
     *
     * @return array<array{array<string, string>, array<string, string>}>
     */
    public static function providerGetDataFromRequestEmpty(): array
    {
        return [
            [
                [
                    'item_name' => '',
                    'item_table' => '',
                    'item_original_name' => '',
                    'item_action_timing' => '',
                    'item_event_manipulation' => '',
                    'item_definition' => '',
                    'item_definer' => '',
                ],
                [
                    'item_name' => '',
                    'item_table' => '',
                    'item_original_name' => '',
                    'item_action_timing' => '',
                    'item_event_manipulation' => '',
                    'item_definition' => '',
                    'item_definer' => '',
                ],
            ],
            [
                [
                    'item_name' => 'foo',
                    'item_table' => 'foo',
                    'item_original_name' => 'foo',
                    'item_action_timing' => 'foo',
                    'item_event_manipulation' => 'foo',
                    'item_definition' => 'foo',
                    'item_definer' => 'foo',
                ],
                [
                    'item_name' => 'foo',
                    'item_table' => 'foo',
                    'item_original_name' => 'foo',
                    'item_action_timing' => 'foo',
                    'item_event_manipulation' => 'foo',
                    'item_definition' => 'foo',
                    'item_definer' => 'foo',
                ],
            ],
        ];
    }

    /**
     * Test for getEditorForm
     *
     * @param array<string, string> $data Data for trigger
     */
    #[DataProvider('providerGetEditorFormAdd')]
    #[Group('medium')]
    public function testGetEditorFormAdd(array $data, string $matcher): void
    {
        $GLOBALS['server'] = 1;
        $this->assertStringContainsString(
            $matcher,
            $this->triggers->getEditorForm('pma_test', 'table', 'add', $data),
        );
    }

    /**
     * Provider for testGetEditorFormAdd
     *
     * @return array<array{array<string, string>, string}>
     */
    public static function providerGetEditorFormAdd(): array
    {
        $data = [
            'item_name' => '',
            'item_table' => 'table1',
            'item_original_name' => '',
            'item_action_timing' => '',
            'item_event_manipulation' => '',
            'item_definition' => '',
            'item_definer' => '',
        ];

        return [
            [$data, 'name="add_item"'],
            [$data, 'name="item_name"'],
            [$data, 'name="item_table"'],
            [$data, 'name="item_timing"'],
            [$data, 'name="item_event"'],
            [$data, 'name="item_definition"'],
            [$data, 'name="item_definer"'],
            [$data, 'name="editor_process_add"'],
        ];
    }

    /**
     * Test for getEditorForm
     *
     * @param array<string, string> $data Data for trigger
     */
    #[DataProvider('providerGetEditorFormEdit')]
    #[Group('medium')]
    public function testGetEditorFormEdit(array $data, string $matcher): void
    {
        $GLOBALS['server'] = 1;
        $this->assertStringContainsString(
            $matcher,
            $this->triggers->getEditorForm('pma_test', 'table', 'edit', $data),
        );
    }

    /**
     * Provider for testGetEditorFormEdit
     *
     * @return array<array{array<string, string>, string}>
     */
    public static function providerGetEditorFormEdit(): array
    {
        $data = [
            'item_name' => 'foo',
            'item_table' => 'table1',
            'item_original_name' => 'bar',
            'item_action_timing' => 'BEFORE',
            'item_event_manipulation' => 'INSERT',
            'item_definition' => 'SET @A=1;',
            'item_definer' => '',
        ];

        return [
            [$data, 'name="edit_item"'],
            [$data, 'name="item_name"'],
            [$data, 'name="item_table"'],
            [$data, 'name="item_timing"'],
            [$data, 'name="item_event"'],
            [$data, 'name="item_definition"'],
            [$data, 'name="item_definer"'],
            [$data, 'name="editor_process_edit"'],
        ];
    }

    /**
     * Test for getEditorForm
     *
     * @param array<string, string> $data Data for trigger
     */
    #[DataProvider('providerGetEditorFormAjax')]
    public function testGetEditorFormAjax(array $data, string $matcher): void
    {
        $GLOBALS['server'] = 1;
        ResponseRenderer::getInstance()->setAjax(true);
        $this->assertStringContainsString(
            $matcher,
            $this->triggers->getEditorForm('pma_test', 'table', 'edit', $data),
        );
        ResponseRenderer::getInstance()->setAjax(false);
    }

    /**
     * Provider for testGetEditorFormAjax
     *
     * @return array<array{array<string, string>, string}>
     */
    public static function providerGetEditorFormAjax(): array
    {
        $data = [
            'item_name' => 'foo',
            'item_table' => 'table1',
            'item_original_name' => 'bar',
            'item_action_timing' => 'BEFORE',
            'item_event_manipulation' => 'INSERT',
            'item_definition' => 'SET @A=1;',
            'item_definer' => '',
        ];

        return [[$data, 'name="editor_process_edit"'], [$data, 'name="ajax_request"']];
    }

    /**
     * Test for getQueryFromRequest
     *
     * @param string $definer    Definer
     * @param string $name       Name
     * @param string $timing     Timing
     * @param string $event      Event
     * @param string $table      Table
     * @param string $definition Definition
     * @param string $query      Query
     * @param int    $numErr     Error number
     */
    #[DataProvider('providerGetQueryFromRequest')]
    public function testGetQueryFromRequest(
        string $definer,
        string $name,
        string $timing,
        string $event,
        string $table,
        string $definition,
        string $query,
        int $numErr,
    ): void {
        $GLOBALS['errors'] = [];

        $_POST['item_definer'] = $definer;
        $_POST['item_name'] = $name;
        $_POST['item_timing'] = $timing;
        $_POST['item_event'] = $event;
        $_POST['item_table'] = $table;
        $_POST['item_definition'] = $definition;
        $GLOBALS['server'] = 1;

        $this->assertEquals($query, $this->triggers->getQueryFromRequest());
        $this->assertCount($numErr, $GLOBALS['errors']);
    }

    /**
     * Data provider for testGetQueryFromRequest
     *
     * @return array<array{string, string, string, string, string, string, string, int}>
     */
    public static function providerGetQueryFromRequest(): array
    {
        return [
            ['', '', '', '', '', '', 'CREATE TRIGGER ON  FOR EACH ROW ', 5],
            [
                'root',
                'trigger',
                'BEFORE',
                'INSERT',
                'table`2',
                'SET @A=NULL',
                'CREATE TRIGGER `trigger` BEFORE INSERT ON  FOR EACH ROW SET @A=NULL',
                2,
            ],
            [
                'foo`s@host',
                'trigger`s test',
                'AFTER',
                'foo',
                'table3',
                'BEGIN SET @A=1; SET @B=2; END',
                'CREATE DEFINER=`foo``s`@`host` TRIGGER `trigger``s test`'
                    . ' AFTER ON  FOR EACH ROW BEGIN SET @A=1; SET @B=2; END',
                2,
            ],
            [
                'root@localhost',
                'trigger',
                'BEFORE',
                'INSERT',
                'table1',
                'SET @A=NULL',
                'CREATE DEFINER=`root`@`localhost` TRIGGER `trigger`'
                    . ' BEFORE INSERT ON `table1` FOR EACH ROW SET @A=NULL',
                0,
            ],
        ];
    }

    public function testGetDetails(): void
    {
        $GLOBALS['cfg']['Server']['DisableIS'] = true;
        $dbiDummy = $this->createDbiDummy();
        $dbiDummy->addResult(
            'SHOW TRIGGERS FROM `test_db`',
            [
                ['test_trigger', 'INSERT', 'test_table', 'BEGIN END', 'AFTER', 'definer@localhost'],
                ['a_trigger', 'UPDATE', 'test_table2', 'BEGIN END', 'BEFORE', 'definer2@localhost'],
            ],
            ['Trigger', 'Event', 'Table', 'Statement', 'Timing', 'Definer'],
        );

        $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db');
        $expected = [
            [
                'name' => 'a_trigger',
                'table' => 'test_table2',
                'action_timing' => 'BEFORE',
                'event_manipulation' => 'UPDATE',
                'definition' => 'BEGIN END',
                'definer' => 'definer2@localhost',
                'full_trigger_name' => '`a_trigger`',
                'drop' => 'DROP TRIGGER IF EXISTS `a_trigger`',
                'create' => "CREATE TRIGGER `a_trigger` BEFORE UPDATE ON `test_table2`\n FOR EACH ROW BEGIN END\n//\n",
            ],
            [
                'name' => 'test_trigger',
                'table' => 'test_table',
                'action_timing' => 'AFTER',
                'event_manipulation' => 'INSERT',
                'definition' => 'BEGIN END',
                'definer' => 'definer@localhost',
                'full_trigger_name' => '`test_trigger`',
                'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`',
                'create' => "CREATE TRIGGER `test_trigger` AFTER INSERT ON `test_table`\n FOR EACH ROW BEGIN END\n//\n",
            ],
        ];
        $this->assertSame($expected, $triggers);
    }

    public function testGetDetails2(): void
    {
        $GLOBALS['cfg']['Server']['DisableIS'] = true;
        $dbiDummy = $this->createDbiDummy();
        $dbiDummy->addResult(
            "SHOW TRIGGERS FROM `test_db` LIKE 'test_table2';",
            [['a_trigger', 'UPDATE', 'test_table2', 'BEGIN END', 'BEFORE', 'definer2@localhost']],
            ['Trigger', 'Event', 'Table', 'Statement', 'Timing', 'Definer'],
        );

        $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db', 'test_table2', '$$');
        $expected = [
            [
                'name' => 'a_trigger',
                'table' => 'test_table2',
                'action_timing' => 'BEFORE',
                'event_manipulation' => 'UPDATE',
                'definition' => 'BEGIN END',
                'definer' => 'definer2@localhost',
                'full_trigger_name' => '`a_trigger`',
                'drop' => 'DROP TRIGGER IF EXISTS `a_trigger`',
                'create' => "CREATE TRIGGER `a_trigger` BEFORE UPDATE ON `test_table2`\n FOR EACH ROW BEGIN END\n$$\n",
            ],
        ];
        $this->assertSame($expected, $triggers);
    }

    public function testGetDetails3(): void
    {
        $GLOBALS['cfg']['Server']['DisableIS'] = false;
        $dbiDummy = $this->createDbiDummy();
        // phpcs:disable Generic.Files.LineLength.TooLong
        $dbiDummy->addResult(
            "SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA COLLATE utf8_bin= 'test_db'",
            [['test_db', 'test_trigger', 'DELETE', 'test_table', 'AFTER', 'BEGIN END', 'test_db', 'test_table', 'definer@localhost']],
            ['TRIGGER_SCHEMA', 'TRIGGER_NAME', 'EVENT_MANIPULATION', 'EVENT_OBJECT_TABLE', 'ACTION_TIMING', 'ACTION_STATEMENT', 'EVENT_OBJECT_SCHEMA', 'EVENT_OBJECT_TABLE', 'DEFINER'],
        );
        // phpcs:enable

        $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db');
        $expected = [
            [
                'name' => 'test_trigger',
                'table' => 'test_table',
                'action_timing' => 'AFTER',
                'event_manipulation' => 'DELETE',
                'definition' => 'BEGIN END',
                'definer' => 'definer@localhost',
                'full_trigger_name' => '`test_trigger`',
                'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`',
                'create' => "CREATE TRIGGER `test_trigger` AFTER DELETE ON `test_table`\n FOR EACH ROW BEGIN END\n//\n",
            ],
        ];
        $this->assertSame($expected, $triggers);
    }

    public function testGetDetails4(): void
    {
        $GLOBALS['cfg']['Server']['DisableIS'] = false;
        $dbiDummy = $this->createDbiDummy();
        // phpcs:disable Generic.Files.LineLength.TooLong
        $dbiDummy->addResult(
            "SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA COLLATE utf8_bin= 'test_db' AND EVENT_OBJECT_TABLE COLLATE utf8_bin = 'test_table';",
            [['test_db', 'test_trigger', 'DELETE', 'test_table', 'AFTER', 'BEGIN END', 'test_db', 'test_table', 'definer@localhost']],
            ['TRIGGER_SCHEMA', 'TRIGGER_NAME', 'EVENT_MANIPULATION', 'EVENT_OBJECT_TABLE', 'ACTION_TIMING', 'ACTION_STATEMENT', 'EVENT_OBJECT_SCHEMA', 'EVENT_OBJECT_TABLE', 'DEFINER'],
        );
        // phpcs:enable

        $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db', 'test_table');
        $expected = [
            [
                'name' => 'test_trigger',
                'table' => 'test_table',
                'action_timing' => 'AFTER',
                'event_manipulation' => 'DELETE',
                'definition' => 'BEGIN END',
                'definer' => 'definer@localhost',
                'full_trigger_name' => '`test_trigger`',
                'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`',
                'create' => "CREATE TRIGGER `test_trigger` AFTER DELETE ON `test_table`\n FOR EACH ROW BEGIN END\n//\n",
            ],
        ];
        $this->assertSame($expected, $triggers);
    }
}
